﻿using System;
using System.Collections.Generic;
using System.Threading;
using net.entity.framework.common;

namespace net.entity.framework.cache {
    /// <summary>
    /// This is global cache for all the entity objects
    /// There will be a maintainance thread that will keep track of all
    /// Stale/Dirty/Deleated objects
    /// </summary>
    internal sealed class EntityCache  {
        #region Members

        private readonly ReaderWriterLockSlim syncLock = new ReaderWriterLockSlim();

        private readonly Dictionary<EntityKey, WeakReferenceEntity> cache =
            new Dictionary<EntityKey, WeakReferenceEntity>();

        private static readonly EntityCache instance = new EntityCache();

        #endregion

        #region Properties

        /// <remarks>
        /// Instance is created to avoid locking on Static types
        /// </remarks>
        public static EntityCache Instance {
            get { return instance; }
        }

        #endregion

        internal bool ContainsKey(EntityKey key) {
            bool contians;
            syncLock.EnterReadLock();
            try {
                contians = cache.LookupContains(key);
            } finally {
                syncLock.ExitReadLock();
            }
            return contians;
        }

        internal bool TryGetValue<T>(EntityKey key, out T value) where T : Entity {
            value = null;
            var valueFoundAndResolved = false;
            WeakReferenceEntity referenceEntity;
            syncLock.EnterReadLock();
            try {
                if (cache.TryGetValue(key, out referenceEntity)) {
                    valueFoundAndResolved = referenceEntity.TryResolveTarget(out value);
                }
            } finally {
                syncLock.ExitReadLock();
            }
            return valueFoundAndResolved;
        }

        internal T GetValue<T>(EntityKey key) where T : Entity {
            T value;
            if (TryGetValue(key, out value)) {
                return value;
            }
            throw new Exception("EntityCache: Error resolving value!");
        }

        internal void PopulateTargets(List<KeyValuePair<EntityKey, Entity>> entityMap) {
            entityMap.ForEach(PopulateTarget);
        }

        private void PopulateTarget(KeyValuePair<EntityKey, Entity> kvpEntity) {
            syncLock.EnterWriteLock();
            try {
                cache[kvpEntity.Key] = new WeakReferenceEntity(kvpEntity.Value);
            } finally {
                syncLock.ExitWriteLock();
            }
        }

        internal void AddEntityRefs<T>(List<T> entityRefs) where T : EntityRef {
            entityRefs.ForEach(AddEntityRef);
        }

        internal void AddEntityRef(EntityRef entityRef) {
            var key = EntityKey.Create(entityRef);
            syncLock.EnterWriteLock();
            try {
                cache[key] = cache.LookupOrDefault(key, new WeakReferenceEntity(null));
            } finally {
                syncLock.ExitWriteLock();
            }
        }

        internal void Remove(EntityRef entityRef) {
            var key = EntityKey.Create(entityRef);
            syncLock.EnterWriteLock();
            var value = cache.Lookup(key, true);
            //value.IsStale = true;
            cache[key] = value;
            syncLock.ExitWriteLock();
        }

        [Obsolete]
        internal void RegisterChanges<T>(IEnumerable<T> entities) where T : Entity {
            //add new objects
            //update existing objects
            //delete old objects
        }
    }

    /// <summary>
    /// Weak entity makes is possible for garbage collector to collect unreferred items.
    /// </summary>
    internal class WeakReferenceEntity : WeakReference {
        internal WeakReferenceEntity(Entity target) : base(target) {}

        internal bool TryResolveTarget<T>(out T entity) where T : Entity {
            entity = Target as T;
            return entity != null;
        }
    }
}